/**
 * 
 */
package com.netflexitysolutions.amazonws.ec2.internal.operations;

import java.lang.reflect.ParameterizedType;

import javax.xml.soap.SOAPFault;
import javax.xml.ws.soap.SOAPFaultException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflexitysolutions.amazonws.ec2.EC2;
import com.netflexitysolutions.amazonws.ec2.EC2Client;
import com.netflexitysolutions.amazonws.ec2.EC2Exception;

/**
 * @author netflexity
 *
 */
public abstract class OperationExecutor<REQUEST, RESPONSE> {

	public static final String UNAVAILABLE = "Unavailable";

	public static final String INSUFFICIENT_INSTANCE_CAPACITY = "InsufficientInstanceCapacity";

	public static final String INSUFFICIENT_ADDRESS_CAPACITY = "InsufficientAddressCapacity";

	public static final String INSUFFICIENT_RESERVED_INSTANCE_CAPACITY = "InsufficientReservedInstanceCapacity";

	public static final String INTERNAL_ERROR = "InternalError";

	private Logger logger = LoggerFactory.getLogger(OperationExecutor.class);
	
	private Class<REQUEST> requestClass;
	private EC2 ec2;
	protected int maxRetries;
	
    /**
     * @param simpleDB
     */
    @SuppressWarnings("unchecked")
	public OperationExecutor(EC2 ec2) {
    	this.ec2 = ec2;
    	requestClass = (Class<REQUEST>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}
	

	public RESPONSE execute() throws EC2Exception{
		if (ec2 instanceof EC2Client) {
			return executeSOAP();
		} else {
			return executeREST();
		}
	}
    
    private RESPONSE executeREST() {
		// TODO Auto-generated method stub
		return null;
	}


	/**
	 * Execute request, process faults (if any) and return appropriate result or exception.
	 * 
	 * @return
	 * @throws EC2Exception
	 */
	private RESPONSE executeSOAP() throws EC2Exception{
		// Create request object.
		REQUEST request = null;
		try {
			request = requestClass.newInstance();
		} catch (InstantiationException e) {
			throw new EC2Exception(e);
		} catch (IllegalAccessException e) {
			throw new EC2Exception(e);
		}
		
		// Invoke operation and return response.
		int retries = 0;
		boolean done = true;
		boolean retry = false;
		RESPONSE response = null;
		do{
			try{
				response = call(request);
			}
			catch(SOAPFaultException e){
				done = true;
				retry = false;
				
				SOAPFault fault = e.getFault();
				if(fault != null){
					String code = fault.getFaultCode();
					logger.debug(code + " - " + fault.getFaultString());
					if(code.indexOf(UNAVAILABLE) > -1){ // - Internal Server Error (equivalent to HTTP 500 error)
						done = false;
						retry = true;
					}
					else if(code.indexOf(INSUFFICIENT_INSTANCE_CAPACITY) > -1){ 
						done = false;
						retry = true;
					}
					else if(code.indexOf(INSUFFICIENT_ADDRESS_CAPACITY) > -1){
						done = false;
						retry = true;
					}
					else if(code.indexOf(INSUFFICIENT_RESERVED_INSTANCE_CAPACITY) > -1){ 
						done = false;
						retry = true;
					}
					else if(code.indexOf(INTERNAL_ERROR) > -1){ 
						done = false;
						retry = true;
					}
					else{
						throw new EC2Exception(fault.getFaultString(), e);
					}
				}
				else{
					throw new EC2Exception(e.getMessage(), e);
				}
			}
			if(retry){
				retries++;
				if (retries > maxRetries) {
					throw new EC2Exception("The number of retries exhausted.");
				}
				retry = false;
				try { 
					Thread.sleep((int)Math.pow(2.0, retries)*1000); 
				} 
				catch (InterruptedException ex) {}
			}
		}
		while(!done);
		
		return response;
	}
	
	/**
	 * Set request parameters if needed and invoke simpleDB operation.
	 * 
	 * @param request
	 * @return
	 * @throws EC2Exception
	 */
	protected abstract RESPONSE call(REQUEST request);

	/**
	 * @return the EC2
	 */
	public EC2 getEC2() {
		return ec2;
	}

	/**
	 * @return the maxRetries
	 */
	public int getMaxRetries() {
		return maxRetries;
	}

	/**
	 * @param maxRetries the maxRetries to set
	 */
	public void setMaxRetries(int maxRetries) {
		this.maxRetries = maxRetries;
	}
}
